//Copyright (c) 2006-2007 Microsoft Corporation.  All rights reserved
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Reflection;

using InvocationFramework.Common;
using InvocationFramework.Common.Interfaces;
using InvocationFramework.Common.Resources;
using InvocationFramework.Common.Exceptions;
using InvocationFramework.Common.Instrumentation;

namespace InvocationFramework.Adapters.Remoting
{
    /// <summary>
    /// </summary>
    /// <author>Sergio Borromei 04/12/2006 20.31</author>
    public class RemotingAdapter : IServiceAdapter
    {
        #region Constructors

        public RemotingAdapter( ) { }
        
        #endregion

        #region Private Vars

        private List<ServiceInfo> _serviceCatalog;

        #endregion

        #region IServiceAdapter members
        
        /// <summary>
        /// Variable privada para el manejo de la propiedad
        /// </summary>
        private string _id;


        /// <summary>
        /// Identificador del adaptador
        /// </summary>
        public string Id
        {
            get
            {
                return _id;
            }
        }

        /// <summary>
        /// Variable privada para el manejo de la propiedad
        /// </summary>
        private bool _initialized;

        /// <summary>
        /// Indica si el adaptador esta inicializado o no
        /// </summary>
        public bool Initialized
        {
            get
            {
                return _initialized;
            }
        }


        /// <summary>
        /// Loads contract type information into adapter service catalog.
        /// </summary>
        /// <remarks>
        /// RemotingAdapter configuration is very similar to any remoting client configuration (<system.runtime.remoting>). It has the same elements
        /// (application, client & wellknown) but also has a "contract" attribute for the wellknown element. This atribute specifies the type that shoould 
        /// be used as contract for the service. If no "contract" is specified, the well known type is used as contract, which means the client should have
        /// local access to a copy of the assembly in which the well known type resides.
        /// The best practice is to separate contracts from service implementations in diferent assemblies. This way, the client only needs to have a copy
        /// of the contract assembly.
        /// If any type (contract or not) couldnt be loaded a warning message is sent to diagnostics sinks (e.g. EventViewer) and the service is not loaded
        /// without cancelling adapter initialization.
        /// </remarks>
        /// <param name="id">Instance name for the RemotingAdapter (it is possible to have many RemotingAdapters configured at the same time)</param>
        /// <param name="configuration">XmlNode with the remoting client configuration.</param>
        public void Init( string id, XmlNode configuration )
        {
            //Id cannot be null or empty
            if ( id == null || id.Length == 0 )
                throw new InvocationFrameworkException( String.Format( CultureInfo.InvariantCulture, Texts.RemotingAdapterIdCantBeNull, id ) );

            //Initializing private vars
            _id = id;
            _serviceCatalog = new List<ServiceInfo>( );

            XmlNodeList wellKnowns = configuration.SelectNodes( "//system.runtime.remoting/application/client/wellknown" );

            //Configuration node must be a valid remoting client configuration
            if ( wellKnowns == null )
                throw new InvocationFrameworkException( String.Format( CultureInfo.InvariantCulture, Texts.RemotingAdapterBadConfig, this.Id ) );

            LoadWellKnownsContracts( wellKnowns );

            _initialized = true;
        }

        /// <summary>
        /// Support method for Init()
        /// </summary>
        /// <param name="wellKnowns"></param>
        private void LoadWellKnownsContracts( XmlNodeList wellKnowns )
        {
            Type contract;

            //Load "Well Known" types as services in catalog for later use.
            foreach ( XmlNode wellKnown in wellKnowns )
            {

                //Check if atribute contract is specified
                if ( wellKnown.Attributes[ "contract" ] != null )
                {
                    contract = Type.GetType( wellKnown.Attributes[ "contract" ].Value, false );

                    //Type cannot be null
                    if ( contract == null )
                    {
                        TraceHelper.Warning( Texts.RemotingAdapterCouldntLoadContract, wellKnown.Attributes[ "contract" ].Value );
                        continue;
                    }
                }
                else //if not, use the well known type as contract
                {
                    contract = Type.GetType( wellKnown.Attributes[ "type" ].Value, false );

                    //Type cannot be null
                    if ( contract == null )
                    {
                        TraceHelper.Warning( Texts.RemotingAdapterCouldntLoadType, wellKnown.Attributes[ "type" ].Value );
                        continue;
                    }
                }


                //Check if serviceName attribute is specified
                string serviceName;
                if ( wellKnown.Attributes[ "serviceName" ] != null && !String.IsNullOrEmpty( wellKnown.Attributes[ "serviceName" ].Value ) )
                    serviceName = wellKnown.Attributes[ "serviceName" ].Value;
                else //If not, use the type name as service name
                    serviceName = contract.Name;


                string url = wellKnown.Attributes[ "url" ].Value;
                if ( url != null )
                {
                    ServiceInfo serviceInfo = new ServiceInfo( contract, serviceName, url );

                    //Load service info into catalog
                    _serviceCatalog.Add( serviceInfo );

                }
                else
                    TraceHelper.Warning( Texts.RemotingAdapterServiceWithoutURL, contract.FullName );

            }
        }


        public bool IsServiceAvailable( string serviceName )
        {
            ServiceFinder svcFinder = new ServiceFinder( serviceName );

            ServiceInfo s = _serviceCatalog.Find( new Predicate<ServiceInfo>( svcFinder.ServiceFindPredicate ) );

            return ( s != null );
        }


        public bool IsServiceOperationAvailable( string serviceName, string operationName )
        {
            ServiceFinder svcFinder = new ServiceFinder( serviceName );

            ServiceInfo s = _serviceCatalog.Find( new Predicate<ServiceInfo>( svcFinder.ServiceFindPredicate ) );

            if ( s == null )
                return false;

            OperationFinder opFinder = new OperationFinder( operationName );

            List<MethodInfo> methods = new List<MethodInfo>( s.Type.GetMethods( ) );

            MethodInfo m = methods.Find( new Predicate<MethodInfo>( opFinder.OperationFindPredicate ) );

            return ( m != null );
        }

        public string[] GetMostRequestedServices( )
        {
            string[] serviceList = new string[ _serviceCatalog.Count ];

            int i = 0;
            foreach ( ServiceInfo si in _serviceCatalog )
            {
                serviceList[ i ] = si.Type.Name;
                i++;
            }

            return serviceList;
        }

        public object Execute( string serviceName, string operationName, ContextDictionary context, params object[] parameters )
        {
            string errorMessage;

            if ( !_initialized )
            {
                errorMessage = String.Format( CultureInfo.InvariantCulture, Texts.RemotingAdapterNotInitialized, serviceName, operationName );
                throw new InvocationFrameworkException( errorMessage );
            }

            //Find the service
            ServiceFinder svcFinder = new ServiceFinder( serviceName );
            ServiceInfo service = _serviceCatalog.Find( new Predicate<ServiceInfo>( svcFinder.ServiceFindPredicate ) );

            if ( service == null )
            {
                errorMessage = String.Format( CultureInfo.InvariantCulture, Texts.RemotingAdapterServiceNotFound, serviceName );
                throw new InvocationFrameworkInvocationException( errorMessage );
            }

            //Find the operation
            OperationFinder opFinder = new OperationFinder( operationName );
            List<MethodInfo> operations = new List<MethodInfo>( service.Type.GetMethods( ) );
            MethodInfo operation = operations.Find( new Predicate<MethodInfo>( opFinder.OperationFindPredicate ) );

            if ( operation == null )
            {
                errorMessage = String.Format( CultureInfo.InvariantCulture, Texts.RemotingAdapterOperationNotFound, serviceName, operationName );
                throw new InvocationFrameworkInvocationException( errorMessage );
            }

            //Get a remote instance of the object
            object serviceInstance = Activator.GetObject( service.Type, service.Url );

            object result;

            try
            {
                //Do the invocation
                result = service.Type.InvokeMember( operationName, BindingFlags.InvokeMethod, null, serviceInstance, parameters, CultureInfo.InvariantCulture );
            }
            catch ( Exception ex )
            {
                //Unwrapp the original exception if any
                if ( ex.InnerException == null )
                    throw;
                else
                    throw ex.InnerException;
            }

            return result;
        }

        #endregion
    }

    /// <summary>
    /// </summary>
    /// <author>Sergio Borromei 04/12/2006 20.31</author>
    internal class ServiceFinder
    {
        #region Private Vars

        private string _serviceToFind;

        #endregion

        #region Constructors

        public ServiceFinder( string serviceToFind )
        {
            _serviceToFind = serviceToFind;
        }

        #endregion

        #region Public Methods

        public bool ServiceFindPredicate( ServiceInfo s )
        {
            return ( s.Name == _serviceToFind );
        }

        #endregion
    }

    /// <summary>
    /// </summary>
    /// <author>Sergio Borromei 04/12/2006 20.31</author>
    internal class OperationFinder
    {
        #region Private Vars

        private string _operationToFind;

        #endregion

        #region Constructors

        public OperationFinder( string operationTofind )
        {
            _operationToFind = operationTofind;
        }

        #endregion

        #region Public Methods

        public bool OperationFindPredicate( MethodInfo m )
        {
            return ( m.Name == _operationToFind );
        }

        #endregion
    }

    /// <summary>
    /// </summary>
    /// <author>Sergio Borromei 04/12/2006 20.31</author>
    internal class ServiceInfo
    {
        #region Constructors

        public ServiceInfo( Type type, string serviceName, string url )
        {
            _url = url;
            _type = type;
            _serviceName = serviceName;
        }

        #endregion

        #region Properties

        private string _url;
        
        public string Url
        {
            get
            {
                return _url;
            }
        }

        private Type _type;
        
        public Type Type
        {
            get
            {
                return _type;
            }
        }

        private string _serviceName;
        
        public string Name
        {
            get
            {
                return _serviceName;
            }
        }

        #endregion
    }
}